feat(android): migrate XML layouts to Jetpack Compose#943
Conversation
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| Documentation | 33 minor |
| ErrorProne | 4 high |
| Complexity | 8 medium |
🟢 Metrics 79 complexity · -1 duplication
Metric Results Complexity 79 Duplication -1
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
Greptile SummaryThis PR migrates the httpSMS Android app's three Activities (Login, Main, Settings) from XML-based views to Jetpack Compose, introducing dedicated
Confidence Score: 5/5Safe to merge — all three screens function correctly with the new Compose implementation, the previous login-freeze regression has been addressed, and no blocking defects were introduced. The core migration is solid: validation logic is preserved, the URI constructor and network calls are properly wrapped in try/catch with isLoading reset on errors, and each ViewModel correctly manages its own StateFlow. The only findings are minor style issues — dead code in LoginActivity, redundant SideEffect status-bar writes in SettingsActivityContent, and a slightly misleading permission guard in autoDetectPhoneNumbers. No files require special attention; the three items flagged are all low-impact style clean-ups. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant A as LoginActivity
participant LVM as LoginViewModel
participant MVM as MainViewModel
participant SVM as SettingsViewModel
A->>LVM: initialize(context, defaultServerUrl)
LVM->>LVM: autoDetectPhoneNumbers()
A->>A: "setContent { HttpSmsTheme { LoginScreen } }"
Note over A,LVM: User taps Login
A->>LVM: login(context, countryCode, ...)
LVM->>LVM: validate (phone, URL)
LVM->>LVM: "withContext(IO) { updateFcmToken }"
LVM-->>A: "uiState.loginSuccess = true"
A->>A: LaunchedEffect → redirectToMain()
Note over A,MVM: MainActivity opened
A->>MVM: initialize(context, appVersion)
MVM->>MVM: updateState() – SMS perms, battery, heartbeat ts
A->>A: "setContent { HttpSmsTheme { MainScreen } }"
Note over A,MVM: User taps Heartbeat
A->>MVM: sendHeartbeat(context, onComplete)
MVM->>MVM: "withContext(IO) { storeHeartbeat }"
MVM-->>A: onComplete(error?)
Note over A,SVM: SettingsActivity opened
A->>SVM: initialize(context)
A->>A: "setContent { HttpSmsTheme { SettingsScreen } }"
Note over A,SVM: Toggle / logout delegates to SettingsViewModel
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant A as LoginActivity
participant LVM as LoginViewModel
participant MVM as MainViewModel
participant SVM as SettingsViewModel
A->>LVM: initialize(context, defaultServerUrl)
LVM->>LVM: autoDetectPhoneNumbers()
A->>A: "setContent { HttpSmsTheme { LoginScreen } }"
Note over A,LVM: User taps Login
A->>LVM: login(context, countryCode, ...)
LVM->>LVM: validate (phone, URL)
LVM->>LVM: "withContext(IO) { updateFcmToken }"
LVM-->>A: "uiState.loginSuccess = true"
A->>A: LaunchedEffect → redirectToMain()
Note over A,MVM: MainActivity opened
A->>MVM: initialize(context, appVersion)
MVM->>MVM: updateState() – SMS perms, battery, heartbeat ts
A->>A: "setContent { HttpSmsTheme { MainScreen } }"
Note over A,MVM: User taps Heartbeat
A->>MVM: sendHeartbeat(context, onComplete)
MVM->>MVM: "withContext(IO) { storeHeartbeat }"
MVM-->>A: onComplete(error?)
Note over A,SVM: SettingsActivity opened
A->>SVM: initialize(context)
A->>A: "setContent { HttpSmsTheme { SettingsScreen } }"
Note over A,SVM: Toggle / logout delegates to SettingsViewModel
Reviews (2): Last reviewed commit: "fix: address PR review comments\n\n- Imp..." | Re-trigger Greptile |
| composeOptions { | ||
| kotlinCompilerExtensionVersion = "1.5.15" | ||
| } |
There was a problem hiding this comment.
When using the
org.jetbrains.kotlin.plugin.compose Gradle plugin (Kotlin 2.0+), the compose compiler is bundled with the Kotlin plugin and the composeOptions { kotlinCompilerExtensionVersion } block is no longer needed. Including version 1.5.15 here alongside the 2.0.21 plugin can produce a build warning and is at minimum confusing.
| composeOptions { | |
| kotlinCompilerExtensionVersion = "1.5.15" | |
| } |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| implementation("androidx.compose.material3:material3") | ||
| implementation("androidx.activity:activity-compose:1.9.3") | ||
| implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7") | ||
| implementation("androidx.navigation:navigation-compose:2.8.3") |
There was a problem hiding this comment.
navigation-compose is added as a dependency but is never used in this PR — all three activities still navigate via explicit Intents. This unused artifact increases build time and APK size needlessly. Remove it if Compose Navigation is not yet planned, or leave a comment explaining its intent.
| implementation("androidx.navigation:navigation-compose:2.8.3") |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Fixed! Removed the unused dependency.
…ViewModel using URLUtil.\n- Added exception handling around URI constructor and network calls.\n- Cleaned up wildcard imports in UI files.\n- Ensured isLoading is reset on error.
|
I've addressed the review comments:
|
|
@greptile-apps Thanks for the thorough review! I've fixed the issues you pointed out:
The PR is now more stable and adheres to the project's coding standards. |
There was a problem hiding this comment.
Pull request overview
This PR migrates the Android app UI from legacy XML layouts to Jetpack Compose + Material3, introducing ViewModels to drive reactive UI state and updating Activities to render via setContent.
Changes:
- Enabled Compose in the Android app module, added Compose/Material3 dependencies, and introduced a Compose theme/typography layer.
- Rebuilt Login, Main, and Settings screens as composables backed by new ViewModels (
LoginViewModel,MainViewModel,SettingsViewModel). - Removed legacy XML activity layouts and refactored Activities to use Compose content instead of
setContentView(...).
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| android/build.gradle.kts | Adds Kotlin + Compose compiler plugins at the Android root level. |
| android/app/build.gradle.kts | Enables Compose and adds Material3/Compose dependencies for the app module. |
| android/app/src/main/res/layout/activity_settings.xml | Removes legacy Settings XML layout (Compose replacement). |
| android/app/src/main/res/layout/activity_main.xml | Removes legacy Main XML layout (Compose replacement). |
| android/app/src/main/res/layout/activity_login.xml | Removes legacy Login XML layout (Compose replacement). |
| android/app/src/main/java/com/httpsms/ui/theme/Typography.kt | Adds Material3 typography definition used by the Compose theme. |
| android/app/src/main/java/com/httpsms/ui/theme/Theme.kt | Introduces HttpSmsTheme and color schemes for Compose screens. |
| android/app/src/main/java/com/httpsms/ui/settings/SettingsViewModel.kt | Adds a Settings ViewModel + state model backed by Settings. |
| android/app/src/main/java/com/httpsms/ui/settings/SettingsActivityContent.kt | Implements the Settings UI in Compose. |
| android/app/src/main/java/com/httpsms/ui/main/MainViewModel.kt | Adds a Main ViewModel handling permissions/battery state and heartbeat sending. |
| android/app/src/main/java/com/httpsms/ui/main/MainActivityContent.kt | Implements the Main dashboard UI in Compose. |
| android/app/src/main/java/com/httpsms/ui/login/LoginViewModel.kt | Adds a Login ViewModel handling validation, auto-detection, and login flow. |
| android/app/src/main/java/com/httpsms/ui/login/LoginActivityContent.kt | Implements the Login UI in Compose. |
| android/app/src/main/java/com/httpsms/SettingsActivity.kt | Refactors SettingsActivity to use Compose + SettingsViewModel. |
| android/app/src/main/java/com/httpsms/MainActivity.kt | Refactors MainActivity to use Compose + MainViewModel and removes XML wiring. |
| android/app/src/main/java/com/httpsms/LoginActivity.kt | Refactors LoginActivity to use Compose + LoginViewModel and updates permission flow. |
| plugins { | ||
| id("com.android.application") | ||
| id("com.google.gms.google-services") | ||
| id("io.sentry.android.gradle") version "6.2.0" | ||
| id("org.jetbrains.kotlin.plugin.compose") | ||
| } |
There was a problem hiding this comment.
Adding \org.jetbrains.kotlin.android\ here actually causes a build conflict: 'Cannot add extension with name \kotlin, as there is an extension already registered'. It seems the plugin is already being applied or configured by the root build file or the Android application plugin. I'll keep it as is since it builds successfully.
| composeOptions { | ||
| kotlinCompilerExtensionVersion = "1.5.15" | ||
| } |
There was a problem hiding this comment.
Fixed! Removed the redundant \composeOptions\ block as the Kotlin 2.0 Compose plugin handles this automatically.
| val uiState by viewModel.uiState.collectAsState() | ||
| val context = LocalContext.current | ||
|
|
There was a problem hiding this comment.
Fixed! Removed the unused \context\ variable.
| if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED && | ||
| ContextCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) != PackageManager.PERMISSION_GRANTED) { | ||
| Timber.d("Permissions not granted for auto-detecting phone numbers") | ||
| return | ||
| } |
There was a problem hiding this comment.
Fixed! Simplified the permission check to \READ_PHONE_STATE\ as suggested to avoid unnecessary security exceptions.
This PR migrates the httpSMS Android application's UI from legacy XML layouts to Jetpack Compose.
Key Changes
Verification